package bancosys.tec.persist.validator;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.persist.PersistMessages;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.PersistenceController;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.persist.hibernate.impl.HibernateDependencyMetaData;
import bancosys.tec.persist.span.SpanAwareBean;
import bancosys.tec.persist.span.SpanAwareValidator;
import bancosys.tec.persist.span.fat.FatSpanAwareBean;
import bancosys.tec.persist.span.fat.FatSpanAwareValidator;
import bancosys.tec.utils.reflection.ReflectionUtils;

/**
 * Factory de Persister.
 */
@SuppressWarnings("deprecation")
public class DefaultValidatorFactory implements ValidatorFactory {

    private PersistenceController persistenceController;

    private ValidatorRegistry validatorRegistry;

    /**
     * Meta Dados contendo as dependências das classes mapeadas no hibernate.
     */
    private HibernateDependencyMetaData hibernateDependencyMetaData;

    private Map<Class<?>, Class<? extends BeanValidator>> validatorClassCache =
            new ConcurrentHashMap<Class<?>, Class<? extends BeanValidator>>();

    private Map<Class<?>, BeanValidator> validatorInstanceCache = new ConcurrentHashMap<Class<?>, BeanValidator>();

    /**
     * Nome do package onde ficam os beans.
     */
    public static final String BEAN_PACKAGE = "domain";

    /**
     * Nome do package onde ficam os dados.
     */
    public static final String VALIDATOR_PACKAGE = "validator";

    /**
     * Define o persistenceController.
     * 
     * @param persistenceController o persistenceController a ser definido.
     */
    public void setPersistenceController(PersistenceController persistenceController) {
        this.persistenceController = persistenceController;
    }

    /**
     * Recupera o BeanValidator a partir de sua classe. Este método assume o padrão que o Validator está no pacote .persiter a partir do
     * pacote da classe, e que possui 'Validator' no fim do nome. Exemplo: Classe: bancosys.tec.persist.domain.base.FirstDummy DAO:
     * bancosys.tec.persist.validator.base.FirstDummyValidator
     * 
     * @param <T> tipo de bean
     * @param bean o bean.
     * @return o validator
     */
    public <T extends Persistable> Validator<T> getValidator(T bean) {
        @SuppressWarnings("unchecked")
        Class<T> beanClass = (Class<T>) bean.getClass();
        CompositeValidator<T> compositeValidator = new CompositeValidator<T>(beanClass);
        compositeValidator.addValidator(this.createLegacyValidator(bean));
        compositeValidator.addAllValidator(this.validatorRegistry.getValidators(beanClass));
        return compositeValidator;
    }

    /**
     * @param <T> tipo de bean
     * @param bean bean
     * @return validator legado, criado via convenção de nomes
     */
    @SuppressWarnings("unchecked")
    private <T extends Persistable> Validator<T> createLegacyValidator(T bean) {
        BeanValidator validator = this.validatorInstanceCache.get(bean.getClass());
        if (validator == null) {
            Class<? extends BeanValidator> validatorClass = this.validatorClassCache.get(bean.getClass());
            if (validatorClass == null) {
                validatorClass = this.getValidatorClass(bean.getClass());
                if (validatorClass == null) {
                    if (bean instanceof SpanAwareBean) {
                        validatorClass = SpanAwareValidator.class;
                    } else if (bean instanceof FatSpanAwareBean<?>) {
                        validatorClass = FatSpanAwareValidator.class;
                    } else {
                        validatorClass = DefaultBeanValidator.class;
                    }
                }
                this.validatorClassCache.put(bean.getClass(), validatorClass);
            }
            try {
                validator = validatorClass.newInstance();
            } catch (SecurityException e) {
                throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_VALIDATOR.create(validatorClass.getName()), e);
            } catch (IllegalArgumentException e) {
                throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_VALIDATOR.create(validatorClass.getName()), e);
            } catch (InstantiationException e) {
                throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_VALIDATOR.create(validatorClass.getName()), e);
            } catch (IllegalAccessException e) {
                throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_VALIDATOR.create(validatorClass.getName()), e);
            }
            validator.setHibernateDependencyMetaData(this.getHibernateDependencyMetaData());
            validator.setPersistenceController(this.persistenceController);

            if (validatorClass.isAnnotationPresent(Unmodifiable.class)) {
                this.validatorInstanceCache.put(bean.getClass(), validator);
            }
        }
        return validator;
    }

    /**
     * Dada uma classe devolve a classe do seu validator.
     * 
     * @param boClass classe.
     * @return classe do validator.
     */
    protected Class<? extends BeanValidator> getValidatorClass(Class boClass) {
        int index = boClass.getName().indexOf("." + DefaultValidatorFactory.BEAN_PACKAGE + ".");
        if (index == -1) {
            throw new PersistenceException(PersistMessages.BEAN_NOT_ON_STANDARD_PACKAGE.create(boClass.getName()));
        }
        String classNamePrefix = boClass.getName().substring(0, index);
        String classNameSuffix = boClass.getName().substring(index + ("." + DefaultValidatorFactory.BEAN_PACKAGE + ".").length());
        String validatorClassName = classNamePrefix + "." + DefaultValidatorFactory.VALIDATOR_PACKAGE + "." + classNameSuffix + "Validator";
        try {
            Class<?> validatorClass = ReflectionUtils.findClass(validatorClassName, this.getClass().getClassLoader());
            if (BeanValidator.class.isAssignableFrom(validatorClass)) {
                return validatorClass.asSubclass(BeanValidator.class);
            }
            return null;
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * Método de acesso para hibernateDependencyMetaData.
     * 
     * @return instância de {@link HibernateDependencyMetaData HibernateDependencyMetaData}.
     */
    public HibernateDependencyMetaData getHibernateDependencyMetaData() {
        return this.hibernateDependencyMetaData;
    }

    /**
     * Método de acesso para hibernateDependencyMetaData.
     * 
     * @param hibernateDependencyMetaData instância de {@link HibernateDependencyMetaData HibernateDependencyMetaData}.
     */
    public void setHibernateDependencyMetaData(HibernateDependencyMetaData hibernateDependencyMetaData) {
        this.hibernateDependencyMetaData = hibernateDependencyMetaData;
    }

    /**
     * @return the validatorRegistry
     */
    public ValidatorRegistry getValidatorRegistry() {
        return this.validatorRegistry;
    }

    /**
     * @param validatorRegistry the validatorRegistry to set
     */
    public void setValidatorRegistry(ValidatorRegistry validatorRegistry) {
        this.validatorRegistry = validatorRegistry;
    }

    /**
     * Registra os validators que serão obtidos na criacao do Validator.
     * 
     * @param listValidator Lista de Validators que serão injetados via spring.
     */

}
